// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SolutionSettingsManager.cs" company="Roman '4ux-nbIx' Novitsky">
//   Copyright (c) Roman '4ux-nbIx' Novitsky 2009
// </copyright>
// <summary>
//   Defines the SolutionSettingsManager type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace SettingsManager
{
  #region Namespace Imports

  using System;
  using System.Collections.Generic;
  using System.Diagnostics.CodeAnalysis;
  using System.IO;
  using System.Linq;
  using System.Threading;

  using JetBrains.Annotations;
  using JetBrains.ComponentModel;
  using JetBrains.ProjectModel;
  using JetBrains.Util;

  #endregion


  /// <summary>
  /// Describes and manipulates the solution settings manager.
  /// </summary>
  [SolutionComponentImplementation]
  [SolutionComponentInterface(ProgramConfigurations.ALL)]
  [UsedImplicitly(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.WithMembers)]
  public sealed class SolutionSettingsManager : ISolutionComponent
  {
    #region Constants and Fields

    private static readonly object _syncRoot = new object();
    private readonly List<FileSystemWatcher> _settingsFileWatchers = new List<FileSystemWatcher>();
    private readonly ISolution _solution;
    private Timer _commonSettingsAutoUpdateTimer;

    #endregion


    #region Constructors and Destructors

    /// <summary>
    /// Initializes a new instance of the <see cref="SolutionSettingsManager"/> class.
    /// </summary>
    /// <param name="solution">
    /// The solution.
    /// </param>
    public SolutionSettingsManager([NotNull] ISolution solution)
    {
      _solution = solution;
      AutoLoadResharperSettings = true;
    }

    #endregion


    #region Events

    /// <summary>
    /// Occurs when the shared settings has changed.
    /// </summary>
    public event EventHandler SharedSettingsChanged;

    #endregion


    #region Properties

    /// <summary>
    /// Gets or sets a value indicating whether auto-load resharper settings.
    /// </summary>
    /// <value>
    /// <c>True</c> if resharper settings should be auto-loaded; otherwise, <c>false</c>.
    /// </value>
    internal bool AutoLoadResharperSettings
    {
      get;
      set;
    }


    /// <summary>
    /// Gets the shared settings file.
    /// </summary>
    /// <value>
    /// The shared settings file.
    /// </value>
    internal SharedSettingsFile SharedSettingsFile
    {
      get;
      private set;
    }

    /// <summary>
    /// Gets the solution.
    /// </summary>
    /// <value>
    /// The solution.
    /// </value>
    internal ISolution Solution
    {
      get
      {
        return _solution;
      }
    }

    #endregion


    #region Public Methods

    /// <summary>
    /// Called after a solution was opened.
    /// </summary>
    public void AfterSolutionOpened()
    {
#if DEBUG
      MessageBox.ShowInfo("You are running the development version of settings manager plug-in.", "Warning!");
#endif

      ReloadSettings();
    }


    /// <summary>
    /// Called before a solution was closed.
    /// </summary>
    public void BeforeSolutionClosed()
    {
      UpdateCommonSettings();
    }


    /// <summary>
    /// Disables the file system monitoring.
    /// </summary>
    [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", 
      Justification = "Don't care.")]
    public void DisableFileSystemMonitoring()
    {
      foreach (FileSystemWatcher fileSystemWatcher in _settingsFileWatchers)
      {
        DisposeFileSystemWatcher(fileSystemWatcher);
      }

      _settingsFileWatchers.Clear();
    }


    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    public void Dispose()
    {
      ResharperSettingsManager manager = ResharperSettingsManager.Instance;
      manager.SettingsAutoUpdateIntervalChanged -= SettingsAutoUpdateIntervalChanged;

      _commonSettingsAutoUpdateTimer.Change(-1, -1);
      _commonSettingsAutoUpdateTimer.Dispose();

      DisableFileSystemMonitoring();
    }


    /// <summary>
    /// Enables the file system monitoring.
    /// </summary>
    [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", 
      Justification = "Don't care.")]
    public void EnableFileSystemMonitoring()
    {
      DisableFileSystemMonitoring();

      string solutionDirectory = _solution.SolutionFilePath.Directory.FullPath;
      string filter = _solution.GetSettingsFileName();

      CreateFileSystemWatcher(solutionDirectory, filter);

      SharedSettingsFile sharedSettingsFile = SharedSettingsFile;
      while (sharedSettingsFile != null)
      {
        if (sharedSettingsFile.ExternalSettingsContainerFilePath != null)
        {
          FileSystemPath path = sharedSettingsFile.ExternalSettingsContainerAbsoluteFilePath;
          CreateFileSystemWatcher(path.Directory.FullPath, path.Name);
        }

        sharedSettingsFile = sharedSettingsFile.ParentSettingsFile;
      }

      FileSystemPath directory = _solution.SolutionFilePath.Directory;

      while (directory != FileSystemPath.Empty)
      {
        string path = directory.FullPath;
        filter = SharedSettingsFile.GlobalSettingsFileName;

        CreateFileSystemWatcher(path, filter);

        directory = directory.Directory;
      }
    }


    /// <summary>
    /// <para>
    /// Initializes the component. Called by the component container after
    /// the component is created.
    /// </para>
    /// <para>
    /// <see cref="M:System.IDisposable.Dispose"/> is the pairing method
    /// that's guaranteed to be called by the component container to tear down
    /// your component.
    /// </para>
    /// <para>
    /// You must not access other components before your 
    /// <see cref="M:JetBrains.ComponentModel.IComponent.Init"/> method is
    /// called.
    /// </para>
    /// <para>
    /// All of the components that you access from 
    /// <see cref="M:JetBrains.ComponentModel.IComponent.Init"/> are guaranteed
    /// to exist when your <see cref="M:System.IDisposable.Dispose"/> is called.
    /// Any other component might be missing by that time, and trying to get it
    /// will throw an exception.
    /// </para>
    /// </summary>
    public void Init()
    {
      _commonSettingsAutoUpdateTimer = new Timer(o => UpdateCommonSettings());

      ResharperSettingsManager manager = ResharperSettingsManager.Instance;
      manager.SettingsAutoUpdateIntervalChanged += SettingsAutoUpdateIntervalChanged;

      SettingsAutoUpdateIntervalChanged(manager, EventArgs.Empty);

      EnableFileSystemMonitoring();
    }
    

    #endregion


    #region Methods

    /// <summary>
    /// Gets the instance of a <see cref="SolutionSettingsManager"/> for a specified solution.
    /// </summary>
    /// <param name="solution">
    /// The solution.
    /// </param>
    /// <returns>
    /// The instance.
    /// </returns>
    [NotNull]
    internal static SolutionSettingsManager GetInstance([NotNull] ISolution solution)
    {
      return solution.GetComponent<SolutionSettingsManager>();
    }


    /// <summary>
    /// Reloads the settings.
    /// </summary>
    internal void ReloadSettings()
    {
      lock (_syncRoot)
      {
        ReloadSettingsFiles();

        if (SharedSettingsChanged != null)
        {
          SharedSettingsChanged(this, EventArgs.Empty);
        }

        if (!AutoLoadResharperSettings)
        {
          return;
        }

        if (Load(SharedSettingsFile))
        {
          return;
        }

        DisableFileSystemMonitoring();

        ResharperSettingsManager settingsManager = ResharperSettingsManager.Instance;

        if (File.Exists(settingsManager.DefaultSettingsFile))
        {
          SettingsFile file = SettingsFile.Load(settingsManager.DefaultSettingsFile);

          if (file != null)
          {
            file.LoadResharperSettings();
          }
        }

        EnableFileSystemMonitoring();
      }
    }


    private static bool Load([CanBeNull] SettingsFile settingsFile)
    {
      if (settingsFile != null)
      {
        if (settingsFile.LoadResharperSettings(ResharperSettingsManager.Instance.ComponentSettingsManagers))
        {
          return true;
        }
      }

      return false;
    }


    private void SettingsAutoUpdateIntervalChanged(object sender, EventArgs e)
    {
      ResharperSettingsManager settingsManager = ResharperSettingsManager.Instance;

      if (settingsManager.SettingsAutoUpdateInterval > 0)
      {
        TimeSpan interval = TimeSpan.FromMinutes(settingsManager.SettingsAutoUpdateInterval);
        _commonSettingsAutoUpdateTimer.Change(interval, interval);
      }
      else
      {
        _commonSettingsAutoUpdateTimer.Change(-1, -1);
      }
    }


    [NotNull]
    [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", 
      Justification = "Don't care.")]
    [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", 
      Justification = "Will be disposed later.")]
    private void CreateFileSystemWatcher([NotNull] string path, [NotNull] string filter)
    {
      if (_settingsFileWatchers.Where(w => w.Filter == filter && w.Path == path).Any())
      {
        return;
      }

      var fileSystemWatcher = new FileSystemWatcher(path, filter)
                                {
                                  NotifyFilter = NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.FileName, 
                                  IncludeSubdirectories = false, 
                                  EnableRaisingEvents = true,
                                };

      fileSystemWatcher.Changed += OnFileSystemChanged;
      fileSystemWatcher.Created += OnFileSystemChanged;
      fileSystemWatcher.Deleted += OnFileSystemChanged;
      fileSystemWatcher.Renamed += OnFileSystemChanged;

      _settingsFileWatchers.Add(fileSystemWatcher);
    }


    [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", 
      Justification = "Don't care.")]
    private void DisposeFileSystemWatcher([NotNull] FileSystemWatcher fileSystemWatcher)
    {
      fileSystemWatcher.EnableRaisingEvents = false;

      fileSystemWatcher.Changed -= OnFileSystemChanged;
      fileSystemWatcher.Created -= OnFileSystemChanged;
      fileSystemWatcher.Deleted -= OnFileSystemChanged;
      fileSystemWatcher.Renamed -= OnFileSystemChanged;
    }


    /// <summary>
    /// Loads the global settings file.
    /// </summary>
    /// <returns>
    /// Returns an instance of <see cref="SettingsManager.SharedSettingsFile"/>.
    /// </returns>
    [CanBeNull]
    private SharedSettingsFile LoadGlobalSettingsFile()
    {
      SharedSettingsFile firstFile = null;
      SharedSettingsFile lastFile = null;

      FileSystemPath directory = _solution.SolutionFilePath.Directory;

      while (directory != FileSystemPath.Empty)
      {
        string filePath = Path.Combine(directory.FullPath, SharedSettingsFile.GlobalSettingsFileName);

        if (File.Exists(filePath))
        {
          var sharedSettingsFile = SharedSettingsFile.Load(Solution, filePath);

          if (lastFile == null)
          {
            lastFile = sharedSettingsFile;
            firstFile = sharedSettingsFile;
          }
          else
          {
            lastFile.ParentSettingsFile = sharedSettingsFile;
            lastFile = sharedSettingsFile;
          }
        }

        directory = directory.Directory;
      }

      return firstFile;
    }


    [CanBeNull]
    private SharedSettingsFile LoadSolutionSettingsFile()
    {
      string solutionSettingsFilePath = _solution.GetSettingsFilePath();

      return !File.Exists(solutionSettingsFilePath) ? null : SharedSettingsFile.Load(Solution);
    }


    /// <summary>
    /// Called when file system has changed.
    /// </summary>
    /// <param name="sender">
    /// The sender.
    /// </param>
    /// <param name="e">
    /// The <see cref="System.IO.FileSystemEventArgs"/> instance containing the event data.
    /// </param>
    private void OnFileSystemChanged([NotNull] object sender, [NotNull] FileSystemEventArgs e)
    {
      ReloadSettings();
    }


    /// <summary>
    /// Reloads the settings files.
    /// </summary>
    private void ReloadSettingsFiles()
    {
      DisableFileSystemMonitoring();

      SharedSettingsFile solutionSettingsFile = LoadSolutionSettingsFile();
      SharedSettingsFile globalSettingsFile = LoadGlobalSettingsFile();

      if (solutionSettingsFile != null)
      {
        solutionSettingsFile.ParentSettingsFile = globalSettingsFile;
        SharedSettingsFile = solutionSettingsFile;
      }
      else
      {
        SharedSettingsFile = globalSettingsFile;
      }

      EnableFileSystemMonitoring();
    }


    /// <summary>
    /// Updates the common settings.
    /// </summary>
    /// <returns>
    /// Returns <c>true</c>, if settings were updated successfully.
    /// </returns>
    [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", 
      Justification = "Don't care.")]
    private bool UpdateCommonSettings()
    {
      if (SharedSettingsFile != null && SharedSettingsFile.AutoUpdateCommonSettings)
      {
        DisableFileSystemMonitoring();

        var settingsSaved = SharedSettingsFile.UpdateCommonResharperSettings();

        EnableFileSystemMonitoring();

        return settingsSaved;
      }

      return true;
    }

    #endregion
  }
}